Day 16 最後說過,主節點掛了,從節點只會一直重連,不會自己升上去。要有人手動把從節點升成主節點,App 連線的位址也要跟著改。
Sentinel(哨兵)就是來做這件事的:盯著主節點,掛了自動挑一台從節點升上去,再告訴 App 新的主節點在哪。
先把 Day 16 的兩台關掉(在主機的終端機下):
docker compose -f docker-compose-replica.yml down
一樣在 Day 2 的 docker-compose.yml 旁邊,另外寫一份 docker-compose-sentinel.yml,一主二從 + 三個 Sentinel:
# Day 17 Sentinel:一主二從 + 三個 Sentinel
name: day17
services:
net: # 什麼都不做,只負責開網路跟 port,其他台都借它的網路
image: redis:7-alpine
container_name: day17-net
command: sleep infinity
init: true # 讓 docker stop 不用等 10 秒
ports:
- "6380-6382:6380-6382" # 三台 Redis
- "26379-26381:26379-26381" # 三個 Sentinel
redis-1:
image: redis:7-alpine
container_name: redis-1
network_mode: service:net # 跟 net 共用網路,彼此用 127.0.0.1 + port 就找得到
command: redis-server --port 6380
redis-2:
image: redis:7-alpine
container_name: redis-2
network_mode: service:net
command: redis-server --port 6381 --replicaof 127.0.0.1 6380
redis-3:
image: redis:7-alpine
container_name: redis-3
network_mode: service:net
command: redis-server --port 6382 --replicaof 127.0.0.1 6380
sentinel-1:
image: redis:7-alpine
container_name: sentinel-1
network_mode: service:net
command: >
sh -c "touch /tmp/sentinel.conf &&
redis-sentinel /tmp/sentinel.conf --port 26379
--sentinel monitor mymaster 127.0.0.1 6380 2
--sentinel down-after-milliseconds mymaster 5000"
sentinel-2:
image: redis:7-alpine
container_name: sentinel-2
network_mode: service:net
command: >
sh -c "touch /tmp/sentinel.conf &&
redis-sentinel /tmp/sentinel.conf --port 26380
--sentinel monitor mymaster 127.0.0.1 6380 2
--sentinel down-after-milliseconds mymaster 5000"
sentinel-3:
image: redis:7-alpine
container_name: sentinel-3
network_mode: service:net
command: >
sh -c "touch /tmp/sentinel.conf &&
redis-sentinel /tmp/sentinel.conf --port 26381
--sentinel monitor mymaster 127.0.0.1 6380 2
--sentinel down-after-milliseconds mymaster 5000"
sentinel monitor mymaster 127.0.0.1 6380 2:盯 6380 這台主節點,取名叫 mymaster。最後的 2 是 quorum,至少 2 個 Sentinel 都覺得它掛了才算數,確保一個 Sentinel 看錯不會亂切down-after-milliseconds:多久沒回應就當它掛了,預設 30 秒,這邊調成 5 秒比較好觀察touch 一個空檔案給它寫network_mode: service:net:Sentinel 會把主節點的 IP 告訴 Java,如果給的是容器自己的 IP(Day 16 ROLE 看到的 172 開頭那種),Docker Desktop 上主機的 Java 連不到。所以讓大家共用 net 的網路,彼此都用 127.0.0.1 + 不同 port 找對方,主機的 Java 連 127.0.0.1 也找得到跟 Day 16 一樣是在同一台電腦模擬,正式環境每台 Redis 跟 Sentinel 都要放在不同機器上。
開起來,看 sentinel-1 的 log(grep "+" 只留 + 開頭的事件,在主機的終端機下):
docker compose -f docker-compose-sentinel.yml up -d
docker logs sentinel-1 | grep "+"

設定裡只寫了主節點,兩台從節點(+slave)跟另外兩個 Sentinel(+sentinel)都是它自己找到的。
在 application.yml 旁邊新增一份 application-sentinel.yml:
# Day 17:啟動時加 --spring.profiles.active=sentinel 才會用這份
# 不寫主節點在哪,改問 Sentinel
spring:
data:
redis:
sentinel:
master: mymaster # 跟 sentinel monitor 取的名字一樣
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381 # 三個 Sentinel
不寫主節點的位址,只寫 Sentinel 在哪,Java 啟動時先問 Sentinel 誰是主節點。
啟動時加上 --spring.profiles.active=sentinel(IntelliJ 在 Run Configuration 的 Program arguments),Java 就改連 Sentinel 這組,不會連 Day 2 的 redis30days(6379)。

開另一個終端機,每秒打一次 Day 4 的 /counter/redis(INCR,每次 +1,在主機的終端機下):
while true; do echo "$(date +%T) $(curl -s localhost:8080/counter/redis)"; sleep 1; done
回到原本的終端機,把主節點關掉(在主機的終端機下):
docker stop redis-1

24 秒送出去的那一筆,等到 34 秒才回來,下一行已經是 35 秒。中間卡了 10 秒左右,但沒有噴錯,數字也接著往下加。
再看 Java 的 console:

斷線後先試著重連 6380,連不上,過了 9 秒自己連到新的主節點。
Sentinel 這邊,三個會先投票選一個出來負責切換(leader),每次選到的不一定,先找出是哪一個(在主機的終端機下):
docker compose -f docker-compose-sentinel.yml logs | grep elected-leader # 誰是 leader
docker logs sentinel-1 | grep -E "\+sdown master|\+odown|elected-leader|selected-slave|switch-master" # sentinel-1 換成上面找到的那一個

找到 leader 是 sentinel-1,後面的 127.0.0.1 6380 是這次掛掉、要被換掉的舊主節點。

+sdown:這個 Sentinel 自己覺得主節點掛了(5 秒沒回應)+odown:#quorum 2/2,有 2 個 Sentinel 都這樣覺得,達到 quorum,才確定掛了+elected-leader:自己被選出來負責切換+selected-slave:挑一台從節點當新的主節點+switch-master:舊的 6380 換成新的主節點,切換完成對一下時間(log 是 UTC,比台灣慢 8 小時):25 秒斷線,Sentinel 等了 5 秒才判定掛了,32 秒切換完,Java 下一次重試才接上,就是 console 上的 34 秒。
先問 Sentinel 現在的主節點在哪(在主機的終端機下):
docker exec sentinel-2 redis-cli -p 26380 SENTINEL get-master-addr-by-name mymaster
大家都擠在同一個網路,每台的 port 都不一樣,redis-cli 要加 -p 指定(sentinel-2 是 26380)。

再把 redis-1 開回來,馬上看它的身分(在主機的終端機下):
docker start redis-1
docker exec redis-1 redis-cli -p 6380 ROLE

Sentinel 已經換成 6381 了,redis-1 卻還說自己是 master。它的啟動參數沒有 --replicaof,一開起來就當自己是主節點,Sentinel 過一陣子才會來處理。
等 15 秒左右再看一次(在主機的終端機下):
docker exec redis-1 redis-cli -p 6380 ROLE

舊主節點回來不會搶回主節點,Sentinel 把它降成 6381 的從節點。
spring.data.redis.timeout 時的預設)才會噴 RedisCommandTimeoutException。主從加 Sentinel 解決了「掛了有人接手」,但能寫的還是只有一台主節點,資料也得一台裝得下。明天來看 Cluster,把資料拆到好幾台上![]()